查看原文
其他

初步打造一站式设计协作平台

腾讯cdc-sachen 腾讯CDC体验设计 2023-01-17
为了解决能随时随地在线跨平台预览设计稿的需求,团队决定孵化一个能在线解析渲染 sketch 文件的项目——Fizz。在上一阶段的研发过程中,基于浏览器端 javascript + canvas + svg 已经初步实现了sketch源文件解析及内容还原的能力,但是仍未具备提供对外渲染接口服务能力,本文将着重讲述该能力的实现过程及服务化的解决方案,并邀请大家一起探讨如何做好一站式设计协作平台及其未来的展望。
众所周知,Sketch 源文件只能在 Sketch 的 Mac 客户端中解析,在前期的研发过程中,我们已经突破了 Sketch 源文件的跨平台预览,现在我们已经把Sketch 解析预览服务上线,并支持把Sketch 文件逐个画板进行解析处理并输出对应的画板预览图,标注信息,切图等。
以下是我们对Sketch 解析预览服务的开发过程:

1. 前端部分处理方案
在上一阶段的开发过程中,我们对Sketch文件内的内容进行深入的调研,对其中的画板Artboard,图层layer,控件symbolmaster等概念的数据定义进行了分析,在明确了其中大量的设计属性,矢量图形,字体文本对应的渲染方式,我们使用基于Canvas的2D渲染库,开发了一套渲染解析处理引擎,对其中Sketch文件内容进行了还原,实现过程主要分为以下几个阶段:
1.1 sketch文件数据提取处理

sketch目录结构简介:

  • images目录为sketch文件内的图片素材资源;

  • pages目录下的json文件,为各个画板下图层属性的数据定义部分;

  • previews, text-previews用于操作系统内快速预览使用;

  • document.json 定义了画板层级结构、控件部分等信息;

  • meta.json 定义了操作记录、依赖字体、sketch版本等信息。

通过对sketch文件对解压提取,可得到如下的目录结构:
1.2 制定sketch to fizz数据模型映射
在第一步提取到pages目录下的json文件中,可以得到每个画板的图层数据定义针对这些零散的Sketch属性,在团队小伙伴们的分工协作下,我们很快整理出了一整套属性表。
并针对这些属性表的内容逐一构建了对应的数据模型映射关系,如字体定义部分:
export const TextAlignmentMap = { left: 0, // Visually left aligned right: 1, // Visually right aligned center: 2, // Visually centered justified: 3, // Fully-justified. The last line in a paragraph is natural-aligned. natural: 4 // Indicates the default alignment for script}
export const TextAlignmentReverseMap = { 0: 'left', // Visually left aligned 1: 'right', // Visually right aligned 2: 'center', // Visually centered 3: 'justified', // Fully-justified. The last line in a paragraph is natural-aligned. 4: 'left' // Indicates the default alignment for script}
export const VerticalTextAlignmentMap = { top: 0, // Visually top aligned middle: 1, // Visually centered bottom: 2 // Visually bottom aligned}
export const VerticalTextAlignmentReverseMap = { 0: 'top', // Visually top aligned 1: 'middle', // Visually centered 2: 'bottom' // Visually bottom aligned}
/** * 参考文档 * https://github.com/jonathantneal/css-font-weight-names * https://www.w3.org/TR/css-fonts-3/#font-weight-prop * https://bigelowandholmes.typepad.com/bigelow-holmes/2015/07/on-font-weight.html */export const FontWeightMap = { thin: 100, hairline: 100, extralight: 200, ultralight: 200, light: 300, book: 400, regular: 400, normal: 400, roman: 400, medium: 500, semibold: 600, demibold: 600, bold: 700, extrabold: 800, ultrabold: 800, heavy: 900, black: 900}

1.3 逐层渲染解析

  • 形状处理(布尔运算,旋转变形,蒙版)

  • 样式填充 (描边,阴影,模糊,渐变,着色器)

  • 字体文本渲染;

  • 位图处理。

const LayerTypeMapClass = { [LAYER_CLASS.TEXT]: Text, [LAYER_CLASS.RECTANGLE]: Rectangle, [LAYER_CLASS.STAR]: Star, [LAYER_CLASS.TRIANGLE]: Triangle, [LAYER_CLASS.POLYGON]: Polygon, [LAYER_CLASS.SHAPEPATH]: ShapePath, [LAYER_CLASS.OVAL]: Oval, [LAYER_CLASS.SHAPEGROUP]: ShapeGroup, [LAYER_CLASS.BITMAP]: Bitmap, [LAYER_CLASS.GROUP]: Group, [LAYER_CLASS.SYMBOLINSTANCE]: SymbolInstance [LAYER_CLASS.SLICE]: Slice}
// 传入图层数据,生成图层数据模型实例export function generateLayerInstance (data) { const constructor = LayerTypeMapClass[data._class] if (constructor) { const layerInstance = new constructor(data) return layerInstance } else { throw new Error(`Unsupported layer type: ${data._class}`) }}

1.4 导出渲染图像内容

浏览器端导出图像内容直接使用cavans相关的api,服务端导出渲染图方案有多种,接下来将重点讲述。

2. 服务化改造
2.1 第一次快速落地尝试
在快速落地尝试的过程中,我们关注到了Puppeteer
Puppeteer提供了一系列API,通过Chrome DevTools Protocol协议控制Chromium/Chrome浏览器的行为在已经搭建了一套前端可视化预览页面的情况下,通过对Puppeteer相关API的调用,传入对应的Fizz页面URL路径,等待浏览器渲染页面完毕后,即可得到渲染引擎解析处理生成的预览截图。
screenshotQueue.process(async (job) => { const { id, page, artboard } = job.data // 待处理的页面地址 const previewUrl = `${process.env.SCREENSHOT_URL}?id=${id}&page=${page}&artboard=${artboard}`
// 创建browser示例 const browser = await puppeteer.connect({ browserWSEndpoint: 'ws://localhost:8081' }) const tab = await browser.newPage()
tab.setViewport({ width: 200, height: 200, deviceScaleFactor: 2 })
// 打开页面,等待响应 await tab.goto(previewUrl, { waitUntil: 'networkidle0' }) await tab.waitForFunction(`window._STATUS_ !== undefined`, { timeout: 30000 })
// 取得页面截图 const preview = await tab.$('#preview') await preview.screenshot({ path: `${TARGET_DIR}/${id}/previews/${artboard}.png` })
await tab.close()
return `${TARGET_DIR}/${id}/previews/${artboard}.png`})

上述方案的确能把成果快速落地,但是在实际使用中,存在CPU、内存占用过高,处理效率低,调用链路复杂等一系列问题,接下来,需要把整套前端处理逻辑迁移至服务端进行实现。

2.2 最终落地方案

由于这套图像解析方案是基于前端开发模式实现的,对浏览器环境及部分DOM API有较多的依赖,在弃用基于Puppeteer的方案之后,迁移到服务端必须对其内部实现过程进行改造,其中包括:

  • browser canvas -> node-canvas(基于cario实现);

  • webgl -> opengles;

  • html dom, svg -> jsdom;

  • css -> 移除并改写实现方式。

在服务端渲染改造的时候,遵循以下思路:

  • 拒绝使用基于浏览器环境的渲染处理,如CSS3document canvas等;

  • 尽量使用成熟的技术,减少试错时间;

  • 素材内容的加载方式改动,本地文件预加载到内存中,不使用http异步获取;

  • 使用基于carionode-canvas取代puppeteer

  • 使用基于js-dompaper.js版本。

在当前版本的canvas2d渲染引擎选择方面,我们使用到了konva.js的NodeJS版本,需安装node-canvas依赖。由于服务端环境与浏览器环境的差异性,实际服务端渲染处理过程中需要对部分逻辑代码进行了改动。
Interface层:
var Konva = require('konva');var canvas = require('canvas'); //node-canvas
// mock windowKonva.window = { Image: canvas.Image, devicePixelRatio: 1,};// mock documentKonva.document = { createElement: function () {}, documentElement: { addEventListener: function () {}, },};
// make some global injectionsglobal.requestAnimationFrame = (cb) => { setImmediate(cb);};
// create canvas in Node envKonva.Util.createCanvasElement = () => { const node = new canvas.Canvas(); node.style = {}; return node;};
// create image in Node envKonva.Util.createImageElement = () => { const node = new canvas.Image(); node.style = {}; return node;};
// _checkVisibility use dom element, in node we can skip itKonva.Stage.prototype._checkVisibility = () => {};
module.exports = Konva;
  • 效果展示


3. 未来展望
我们团队一直在致力于【一站式的设计协作平台】的开发,下一阶段我们会深入研究更复杂的应用场景和实践方案,并计划把现有的解析处理能力往在线编辑方向发展。

基于目前对市场上多个在线设计协作工具的技术架构的调研和收集到的资料,未来有几个技术方向值得我们关注:

  1. 基于WebAssembly + WebGL的浏览器端图形处理框架及其相关工具集;

  2. 搭建基于向量网络的矢量图形编辑工具集;

  3. 完全基于SVG的矢量图形处理方案。


4. 参考资料
  • 布尔逻辑

  • Node-canvas

  • Webassembly令Figma处理性能提高了3倍

  • Path2D API

 







快乐工作,快乐生活

Happy work , Happy life


/


Join us



您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存